#include <string.h>
#include <unistd.h>

#include "CPU.h"
#include "base/Simulator.h"
#include "base/isa_ext.h"
#include "base/signals/SigHandler.h"
#include "base/signals/SigManager.h"
#include "base/simapi.h"

NEW_DELETE_OPERATORS_CPP(SigHandler,100);
Pool* SignalStackPool = NEW Pool("SignalStackPool",
                                 SIGNAL_STACK_SIZE + STACK_ALIGNMENT_SIZE,
                                 100,
                                 0,
                                 false);

bool SigHandler::initializedClass = false;
void SigHandler::initializeClass () {
    if (initializedClass) {
        return;
    }
    initializedClass = true;

    global_sim->register_isa_ext(ISA_EXT_SIGEXIT, &isa_ext_sigexit);
}

SigHandler::SigHandler (CPU* cpu) :
    stack_unaligned_start((Byte*)SignalStackPool->calloc(SIGNAL_STACK_SIZE + STACK_ALIGNMENT_SIZE, __FILE__, __LINE__)),
    stack_aligned_end((Byte*)(((unsigned int) stack_unaligned_start + SIGNAL_STACK_SIZE) & ~(STACK_ALIGNMENT_SIZE -1))),
    runningcpu(cpu),
    savedcpu(NEW Checkpoint())
{
    initializeClass();
    // for now, the registered user sig_stack is ignored
    // create new stack
    signalLog->i("[SigHandler] stack_unaligned_start[%d]=%p\n", cpu->getID(), stack_unaligned_start);
    signalLog->i("[SigHandler] stack_aligned_end    [%d]=%p\n", cpu->getID(), stack_aligned_end);
}

SigHandler::~SigHandler () {
    SignalStackPool->free(stack_unaligned_start,
                          SIGNAL_STACK_SIZE + STACK_ALIGNMENT_SIZE);
    delete savedcpu;
}

// ---------------------------------------------
// these two are application code
// ---------------------------------------------

void SigHandler::afterSignalHandler () {
    ISA_EXT_0_ARG(ISA_EXT_SIGEXIT);
}

bool SigHandler::isa_ext_sigexit (Processor* P,
                                  Reg arg0,
                                  Reg arg1,
                                  Reg* result)
{
    signalLog->i("[SigHandler] sigExit\n");

    P->getSigHandler()->trampolineToKernel();
    // jikes' sig handler returns without calling sigreturn() when it has a
    // stack size problem.  it tries to get dumpstack by changing pc and lr
    // assuming that the same siginfo and ucontext instances are used on the
    // same stack for nested signals.  for now, ignore this.(actually, this is
    // a Mac OS X vs linux/ppc difference, not JikesRVM issue)

    return false;
}

// ---------------------------------------------
// public function from here
// ---------------------------------------------

// called by Simulator or SigManager to jump to app's sig handlers. It
// creates a new context for this signal then has that context switch
// the cpu to run the signal handler, then updates its flag to know it
// is currently handling a signal

void SigHandler::trampolineToSigHandler (SigContext* sigContext) {
    signalLog->i("[SigHandler] getting ready to call a user signal handler for SIG%s(%d) "
                 "current signal nesting depth %d\n", 
                 getSigName(sigContext->signum), sigContext->signum,
                 sigContexts.size());
    // bdc: There is no logical reason to limit nesting depth here.
    // However, usually a high level of nesting implies you have a bug
    // in your signal handler...
    ASSERT(sigContexts.size() < 3);
    sigContexts.insert(sigContexts.end(), sigContext);
    sigContext->switchToSigHandling(
        runningcpu, 
        savedcpu, 
        sigContexts.size() == 1 ? stack_aligned_end : NULL);
}

// called by SigManager to come back to the state before a signal

// tell out sigContext to switch back to the cpu status it saved,
// then delete it and update our own state
void SigHandler::trampolineToKernel () {
    SigContext* sigContext = sigContexts.back();
    sigContext->switchToNormalExec(runningcpu);
    cleanupState();
}

void SigHandler::cleanupState () {
    ASSERT(sigContexts.size() >= 1);
    SigContext* sigContext = sigContexts.back();
    signalLog->i("[SigHandler] %lli a user signal handler for SIG%s is ending "
                 "current signal nesting depth %d\n", 
                 global_sim->getSimclock(),
                 getSigName(sigContext->signum),
                 sigContexts.size());
    sigContexts.pop_back();
    unblockSignal(sigContext);
    delete sigContext;
}

// called by SigManager before running asynchronous signals
// called by CPU before running synchronous signals
void SigHandler::blockSignal (SigContext *sigContext) {
    if (sigprocmask(SIG_SETMASK,
                    global_sim->getSM()->getSigMask(sigContext->signum),
                    &(sigContext->savedset)) != 0)
    {
        Log->e("[SigHandler] failed to set a signal mask for SIG%s\n",
               getSigName(sigContext->signum));
    }
    signalLog->i("[SigHandler] set a signal mask for SIG%s\n",
                 getSigName(sigContext->signum));
}

// called by SigManager after app's sig handler returns
void SigHandler::unblockSignal (SigContext *sigContext) {
    if (sigprocmask(SIG_SETMASK, &(sigContext->savedset), NULL) != 0) {
        Log->e("[SigHandler] failed to unset a signal mask for SIG%s\n",
               getSigName(sigContext->signum));
    }
    signalLog->i("[SigHandler] unset a signal mask for SIG%s\n",
                 getSigName(sigContext->signum));
}

// called when application code calls OS sigreturn() in syscalls.cpp
void SigHandler::sigreturn (ucontext_t* ctx) {
    signalLog->i("[SigHandler] sigreturn\n");
    ASSERT(ctx != NULL);
    // on BSD OS's like Mac OS X, the sigreturn call is used to specify
    // the context to return to. We default sigreturnCtx to the passed
    // in context but this will override it if necessary to
    // an applicaiton specified value.
    sigContexts.back()->sigreturnCtx = ctx;

    // sigReturn does not return, so we need to change to resume in
    // our standard post signal handling code.
    runningcpu->setPC((Reg)ADDR_UINT64((void*)afterSignalHandler));
}

bool SigHandler::hasSignals () {
    return (sigContexts.size() >= 1);
}

